﻿//-------------------------------------------------------------------
//	Copyright (c) 2012  Zhirnov Andrey
//	This file is part of the "OGL-Tools" project.
//	See copyright notice in "HeaderGenerator.h".
//-------------------------------------------------------------------

#include "CodeGenerator.h"

namespace HeaderGenerator
{

	static const uni_c_string	s_aGLTypes[] = {
		"void", "const",
		"GLenum", "GLboolean", "GLbitfield", "GLbyte", "GLshort", "GLint",
		"GLsizei", "GLubyte", "GLushort", "GLuint", "GLfloat", "GLclampf",
		"GLdouble", "GLclampd", "GLvoid",
		"GLchar", "GLintptr", "GLsizeiptr", "GLintptrARB", "GLsizeiptrARB",
		"GLcharARB", "GLhandleARB", "GLhalfARB", "GLhalfNV", "GLint64EXT",
		"GLuint64EXT", "GLint64", "GLuint64", "GLsync", "_cl_context",
		"_cl_event", "GLvdpauSurfaceNV", "GLDEBUGPROCARB", "GLDEBUGPROCAMD",
		"GLDEBUGPROC", "struct",
		// wgl
		"HPBUFFERARB", "HPBUFFEREXT", "HVIDEOOUTPUTDEVICENV", "HPVIDEODEV",
		"HPGPUNV", "HGPUNV", "GPU_DEVICE", "PGPU_DEVICE", "HVIDEOINPUTDEVICENV",
		"HANDLE", "HDC", "int", "UINT", "VOID", "BOOL", "FLOAT", "HGLRC", "char",
		"INT64", "USHORT", "DWORD", "LPVOID", "float", "unsigned", "long", "LPCSTR",
		"PROC", "LPGLYPHMETRICSFLOAT", "LPLAYERPLANEDESCRIPTOR", "CONST", "COLORREF",
		"WGLSWAP", "INT32", "PIXELFORMATDESCRIPTOR", "LPPIXELFORMATDESCRIPTOR",
		// glX
		"XVisualInfo", "Display", "GLXContext", "Bool", "GLXDrawable", "GLXPixmap",
		"Pixmap", "GLXFBConfig", "GLXWindow", "GLXPbuffer", "__GLXextFuncPtr", "uint64_t",
		"int64_t", "GLXVideoSourceSGIX", "GLXFBConfigIDSGIX", "GLXFBConfigSGIX",
		"GLXPbufferSGIX", "GLXBufferClobberEventSGIX", "GLXVideoDeviceNV",
		"GLXVideoCaptureDeviceNV", "GLXContextID", "Status", "GLXHyperpipeNetworkSGIX",
		"GLXHyperpipeConfigSGIX", "GLXPipeRect", "GLXPipeRectLimits"
	};

	static const char	s_aOutputFileData[] = "/*\n\tThis file generated by CodeGenerator from \"OGL-Tools\" Project\n*/\n\n";
	static const char	s_aEnumBegin[]		= "enum type\n{\n";
	static const char	s_aEnumEnd[]		= "};\n";
	static const char	s_aFileBegin[]		= "\n\t/*file: ";
	static const char	s_aFileEnd[]		= " */\n";
	static const char	s_aFuncPrefix[]		= "\tGL_BUILDFUNC( ";
	static const char	s_aFuncBegin[]		= "#define ARGS(...)\t\t\t\t\t\t__VA_ARGS__\n"
											  "#define SWITCH_VOID( _void, _other )\t_void\n"
											  "#define SWITCH_OTHER( _void, _other )\t_other\n"
											  "inline void DEFAULT_HOOK(...) {}\n\n";
	static const char	s_aDefFile[]		= "LIBRARY opengl32\nEXPORTS\nDllInitialize = DllMain PRIVATE\n\n";

	
	inline bool IsIdentifer(char c)
	{
		return ( c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z' ) );
	}

	inline bool IsWordSymbol(char c)
	{
		return ( IsIdentifer(c) || (c >= '0' && c <= '9') );
	}

	inline bool IsNumber16(char c)
	{
		return ( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') );
	}
	
	inline bool IsNumber10(char c)
	{
		return (c >= '0' && c <= '9');
	}

	inline void ParseSpaceTabComments(const uni_c_string &sSource, usize &uPos)
	{
		usize &			i = uPos;
		const char *	s = sSource.cstr();

		for (; i < sSource.Length(); ++i)
		{
			if ( s[i] == 32 || s[i] == 9 )	// space or tab
				continue;

			if ( s[i] == '/' && s[i+1] == '/' )	// one line comment
			{
				i += 2;
				while ( s[i] != 13 || s[i] != 10 ) { ++i; }
				continue;
			}

			if ( s[i] == '/' && s[i+1] == '*' )	// multiline comment
			{
				i += 2;
				while ( s[i] != '*' && s[i+1] != '/' ) { ++i; }
				i += 2;
				continue;
			}

			if ( s[i] == 13 || s[i] == 10 )
				continue;

			break;
		}
	}

	inline void ReadIdentifer(const uni_c_string &sSource, usize &uPos, string &sIdent)
	{
		usize &			i = uPos;
		const char *	s = sSource.cstr();

		while ( IsWordSymbol( s[i] ) ) { sIdent << s[i];  ++i;}
	}

	inline bool IsGLType(const uni_c_string &sIdent)
	{
		const uni_array<const uni_c_string>		a_types( s_aGLTypes );

		FOR( i, a_types ) {
			if ( a_types[i] == sIdent ) {
				return true;
			}
		}
		return false;
	}

	inline void OnlyArgNames(string &sResult, usize uOffset, usize uLength)
	{
		usize	i = uOffset;
		string	temp;

		for (; i < uLength; ++i)
		{
			if ( IsIdentifer( sResult[i] ) )
			{
				temp.ClearMem();
				ReadIdentifer( sResult, i, temp );

				if ( !IsGLType( temp ) )
					sResult << temp << ", ";
			}
		}

		if ( sResult.Back() == ' ' )	sResult.PopBack();
		if ( sResult.Back() == ',' )	sResult.PopBack();
	}


/*
=================================================
	конструктор
=================================================
*/
	CCodeGenerator::CCodeGenerator()
	{
	}
	
/*
=================================================
	деструктор
=================================================
*/
	CCodeGenerator::~CCodeGenerator()
	{
	}
	
/*
=================================================
	генерация кода
=================================================
*/
	bool CCodeGenerator::Generate(	const uni_array<const char *> &aInputHeaders,
									const char * pOverride,
									const char * pOutputFuncs,
									const char * pOutputEnums,
									const char * pOutputDef  )
	{
		CHECK_RETURN( _ProcessOverride( pOverride ) );
		CHECK_RETURN( FILESYS()->OpenFile( pOutputFuncs, _pOutputFuncs, e_file_open_flags::DEF_WRITE ) );
		CHECK_RETURN( FILESYS()->OpenFile( pOutputEnums, _pOutputEnums, e_file_open_flags::DEF_WRITE ) );
		CHECK_RETURN( FILESYS()->OpenFile( pOutputDef,	 _pOutputDef,	e_file_open_flags::DEF_WRITE ) );

		_pOutputFuncs->WriteS( s_aOutputFileData, count_of(s_aOutputFileData)-1 );
		_pOutputEnums->WriteS( s_aOutputFileData, count_of(s_aOutputFileData)-1 );
		_pOutputEnums->WriteS( s_aEnumBegin,		  count_of(s_aEnumBegin)-1 );
		_pOutputFuncs->WriteS( s_aFuncBegin,		  count_of(s_aFuncBegin)-1 );
		_pOutputDef->WriteS(   s_aDefFile,		  count_of(s_aDefFile)-1 );

		FOR( i, aInputHeaders )
		{
			const uni_c_string	s_filename = String::GetFileName( uni_c_string( aInputHeaders[i] ) );
			string				s_name;

			String::GetFileNameStr( s_filename, s_name );
			String::ToUpper( s_name );
			s_name >> "#define " << "_FUNCTIONS \\\n";

			_pOutputFuncs->WriteS( s_aFileBegin,			count_of(s_aFileBegin)-1 );
			_pOutputEnums->WriteS( s_aFileBegin,			count_of(s_aFileBegin)-1 );
			_pOutputFuncs->WriteS( s_filename.cstr(),	s_filename.Length() );
			_pOutputEnums->WriteS( s_filename.cstr(),	s_filename.Length() );
			_pOutputFuncs->WriteS( s_aFileEnd,			count_of(s_aFileEnd)-1 );
			_pOutputEnums->WriteS( s_aFileEnd,			count_of(s_aFileEnd)-1 );
			_pOutputFuncs->WriteS( s_name.cstr(),		s_name.Length() );


			CHECK_RETURN( _ProcessHeader( aInputHeaders[i] ) );
		}
		
		_pOutputEnums->WriteS( s_aEnumEnd, count_of(s_aEnumEnd)-1 );

		_pOutputFuncs = nullptr;
		_pOutputEnums = nullptr;
		_aOverriden.Clear();
		return true;
	}
	
/*
=================================================
	
=================================================
*/
	void CCodeGenerator::_ProcessFunctions(const uni_c_string &sData, const uni_c_string &sPrefix, usize uOffset)
	{
		usize		u_pos = 0;
		string		temp;


		// fund functions
		u_pos = 0;
		while ( sData.Find( sPrefix, u_pos, u_pos ) )
		{
			temp.ClearMem();
			
			usize	u_params_begin	= 0,
					u_params_end	= 0;
			usize	u_last_pos		= u_pos;
			bool	b_is_void		= false;
			bool	b_has_hook		= false;
			string	s_hook_name;


			// read return value
			bool	b_ret_val = false;
			while ( sData[u_pos] != 13 && sData[u_pos] != 10 ) { --u_pos; }
			while ( ++u_pos < u_last_pos )
			{
				ParseSpaceTabComments( sData, u_pos );

				if ( IsIdentifer( sData[u_pos] ) )
				{
					usize	u_offset = temp.Length();

					ReadIdentifer( sData, u_pos, temp );

					bool	b_is_gl_type = IsGLType( uni_c_string( temp.cstr() + u_offset, temp.Length() - u_offset ) );

					if ( b_is_gl_type ) {
						b_ret_val = true;
						temp << ' ';
					}
					else
						temp.SetLength( u_offset );
				}

				if ( sData[u_pos] == '*' && b_ret_val ) {
					temp << "* ";
					continue;
				}
			}
			
			if ( temp.Empty() ) {
				u_pos = u_last_pos+1;
				continue;
			}
			temp.Back() = ',';
			temp << ' ';

			b_is_void = (temp == "void, ") || (temp == "GLvoid, ") || (temp == "VOID, ");


			// read func name
			u_pos = u_last_pos + sPrefix.Length() - uOffset;
			ParseSpaceTabComments( sData, u_pos );

			if ( IsIdentifer( sData[u_pos] ) )
			{
				u_params_begin = temp.Length();
				ReadIdentifer( sData, u_pos, temp );
				u_params_end = temp.Length();

				b_has_hook = _CheckHook(  uni_c_string(	(void *)(temp.cstr() + u_params_begin),
														(void *)(temp.cstr() + u_params_end+1) ),
										  s_hook_name );
			}
			else
				continue;


			// read params
			ParseSpaceTabComments( sData, u_pos );

			if ( sData[u_pos] == '(' )
			{
				temp << "\n";
				_pOutputDef->WriteS( temp.cstr() + u_params_begin, temp.Length() - u_params_begin );
				temp.PopBack();

				temp << ", ARGS(";	++u_pos;
				usize	u_num_brackets = 1;
				u_params_begin = temp.Length();

				for (; sData[u_pos] != ';'; ++u_pos)
				{
					ParseSpaceTabComments( sData, u_pos );

					if ( IsIdentifer( sData[u_pos] ) ) {
						ReadIdentifer( sData, u_pos, temp );
						temp << ' ';	--u_pos;
						continue;
					}
					if ( sData[u_pos] == ',' && temp.Back() == ' ' ) {
						temp.PopBack();
						temp << ", ";
						continue;
					}
					if ( sData[u_pos] == ')' ) {
						if ( --u_num_brackets == 0 )
							break;
					}
					if ( sData[u_pos] == '(' )
						++u_num_brackets;
						

					temp << sData[u_pos];
				}
				if ( temp.Back() == ' ' )	temp.PopBack();
				u_params_end = temp.Length();
				temp << "),    ARGS(";
			}
			else
				continue;

			OnlyArgNames( temp, u_params_begin, u_params_end );
			temp >> s_aFuncPrefix;

			if ( b_has_hook )	temp << "), " << s_hook_name;
			else				temp << "), DEFAULT_HOOK";
			
			if ( b_is_void )	temp << ", SWITCH_VOID ) \\\n";
			else				temp << ", SWITCH_OTHER ) \\\n";

			_pOutputFuncs->WriteS( temp.cstr(), temp.Length() );
		}
	}
	
/*
=================================================
	
=================================================
*/
	void CCodeGenerator::_ProcessEnums(const uni_c_string &sData, const uni_c_string &sPrefix, usize uOffset)
	{
		usize		u_pos = 0;
		string		temp;


		// find enums
		while ( sData.Find( sPrefix, u_pos, u_pos ) )
		{
			temp.ClearMem();

			u_pos += sPrefix.Length() - uOffset;
			usize	u_last_pos = u_pos;

			// read name
			ReadIdentifer( sData, u_pos, temp );

			// read value
			ParseSpaceTabComments( sData, u_pos );

			// 0x***
			if ( sData[u_pos] == '0' && ( sData[u_pos+1] == 'x' || sData[u_pos+1] == 'X' ) )
			{
				u_last_pos = u_pos;
				u_pos += 2;
				while ( IsNumber16( sData[u_pos++] ) ) {}

				// check type postfix u, ull
				if ( sData[u_pos] == 'u' )	++u_pos;
				if ( sData[u_pos] == 'l' )	++u_pos;
				if ( sData[u_pos] == 'l' )	u_pos += 2;	// ull

				temp << " = ";
				temp << uni_c_string( sData.cstr() + u_last_pos, u_pos - u_last_pos-1 );
			}
			else
			
			// <number>
			if ( IsNumber10( sData[u_pos] ) )
			{
				// TODO: ignore macro
				u_last_pos = u_pos;
				while ( IsNumber10( sData[u_pos++] ) ) {}
				
				// check type postfix u, ull
				if ( sData[u_pos] == 'u' )	++u_pos;
				if ( sData[u_pos] == 'l' )	++u_pos;
				if ( sData[u_pos] == 'l' )	u_pos += 2;	// ull

				temp << " = ";
				temp << uni_c_string( sData.cstr() + u_last_pos, u_pos - u_last_pos-1 );
			}
			else
			
			// GL_***
			if ( sData[u_pos] == 'G' && sData[++u_pos] == 'L' )
			{
				temp << " = ";
				ReadIdentifer( sData, --u_pos, temp );
			}
			else
				continue;

			temp >> '	';
			temp << ",\n";
			_pOutputEnums->WriteS( temp.cstr(), temp.Length() );
		}
	}
	
/*
=================================================
	
=================================================
*/
	bool CCodeGenerator::_ProcessHeader(const char *pHeader)
	{
		// enum mask:	#define GL_***   0x***\n
		// func mask:	WINGDIAPI/GLAPI *** APIENTRY gl*** (***);\n

		// gl
		static const char	s_aGLEnum[]  = "#define GL_";
		static const char	s_aGLFunc[]  = "APIENTRY gl";
		// wgl
		static const char	s_aWGLEnum[] = "#define WGL_";
		static const char	s_aWGLFunc[] = "WINAPI wgl";
		// glX
		static const char	s_aGLXEnum[] = "#define GLX_";
		static const char	s_aGLXFunc[] = " glX";

		CHECK_ARGUMENT( pHeader != nullptr );

		IFilePtr	p_file;
		string		s_data;

		CHECK_RETURN( FILESYS()->OpenFile( pHeader, p_file, e_file_open_flags::DEF_READ ) );

		CHECK_ALLOC( s_data.Reserve( p_file->GetSize()+5 ) );
		CHECK_RETURN( p_file->ReadS( s_data.ptr(), p_file->GetSize() ) );

		s_data.SetLength( p_file->GetSize() );
		p_file = nullptr;

		_ProcessFunctions( s_data, s_aGLFunc,  2 );
		_ProcessFunctions( s_data, s_aWGLFunc, 3 );
		_ProcessFunctions( s_data, s_aGLXFunc, 3 );

		_ProcessEnums( s_data, s_aGLEnum,  3 );
		_ProcessEnums( s_data, s_aWGLEnum, 4 );
		_ProcessEnums( s_data, s_aGLXEnum, 4 );

		return true;
	}

/*
=================================================
	
=================================================
*/
	bool CCodeGenerator::_ProcessOverride(const char *pOverride)
	{
		// mask:	<overriden> = <hook>\n

		if ( pOverride == nullptr )
			return true;

		IFilePtr	p_file;
		string		s_data;

		CHECK_RETURN( FILESYS()->OpenFile( pOverride, p_file, e_file_open_flags::DEF_READ ) );

		CHECK_ALLOC( s_data.Reserve( p_file->GetSize()+5 ) );
		CHECK_RETURN( p_file->ReadS( s_data.ptr(), p_file->GetSize() ) );

		s_data.SetLength( p_file->GetSize() );
		p_file = nullptr;


		string	temp;
		usize	u_pos = 0;
		bool	b_read_hook	= false;

		for (; u_pos < s_data.Length(); ++u_pos)
		{
			if ( IsIdentifer( s_data[u_pos] ) )
			{
				temp.ClearMem();
				ReadIdentifer( s_data, u_pos, temp );
			}

			if ( s_data[u_pos] == '=' )
			{
				usize	u_last_pos = ++u_pos;
				while ( s_data[u_pos] != 13 && s_data[u_pos] != 10 ) { ++u_pos; }

				if ( !temp.Empty() && u_pos > u_last_pos )
				{
					_aOverriden.PushBack( THook() );
					_aOverriden.Back().sOverriden.SetString( temp );
					_aOverriden.Back().sHookName = uni_c_string( s_data.cstr() + u_last_pos, u_pos - u_last_pos );
					continue;
				}
			}
		}

		return true;
	}
	
/*
=================================================
	
=================================================
*/
	bool CCodeGenerator::_CheckHook(const uni_c_string &sFuncName, string &sHookName)
	{
		FOR( i, _aOverriden )
		{
			if ( _aOverriden[i].sOverriden == sFuncName ) {
				sHookName = _aOverriden[i].sHookName;
				return true;
			}
		}
		return false;
	}

}	// HeaderGenerator
